package com.org.oracle.service.impl;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.mongodb.lang.Nullable;
import com.org.oracle.constant.DayLogConstants;
import com.org.oracle.constant.RedisKeyConstants;
import com.org.oracle.constant.TaskConstants;
import com.org.oracle.constant.TaskListConstants;
import com.org.oracle.enums.ResponseCodeEnum;
import com.org.oracle.exception.CustomException;
import com.org.oracle.mysql.entity.DayLog;
import com.org.oracle.service.UserAnswerDetailsService;
import com.org.oracle.sub.entity.TaskList;
import com.org.oracle.mysql.entity.Task;
import com.org.oracle.service.DayLogService;
import com.org.oracle.service.TaskListService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.*;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author heiye
 * @version 1.0
 * @data 2024/8/19 下午10:36
 */
@Slf4j
@Service
public class TaskListServiceImpl implements TaskListService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private DayLogService dayLogService;

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private UserAnswerDetailsService userAnswerDetailsService;

    // 定时任务需要间隔的用户个数
    private static final long SPACER = 50000;

    /**
     * 添加任务到当日的表单中
     *
     * @param uid
     * @param task
     */
    @Override
    public void taskAdd(String uid, Task task) {
        // 检查参数
        Preconditions.checkArgument(StringUtils.isNotBlank(uid) && Objects.nonNull(task), ResponseCodeEnum.PARAM_REQUIRE);

        // 查找该用户的任务列表是否存在
        TaskList taskList = (TaskList) redisTemplate.opsForValue().get(RedisKeyConstants.TASK_LIST + uid);

        // 用户的任务列表不存在
        if (taskList == null) {
            // 封装任务列表
            taskList = TaskList.builder()
                    .uid(uid)
                    .circleId(task.getCircleId())
                    .list(List.of(task))
                    .isFinish(false)
                    .build();

            // 创建 take_list_uid 的键值
            redisTemplate.opsForValue().set(RedisKeyConstants.TASK_LIST + uid, taskList);
            return;
        }

        // 更新用户任务列表
        taskList.getList().add(task);

        // 重新赋值
        redisTemplate.opsForValue().set(RedisKeyConstants.TASK_LIST + uid, taskList);
    }

    /**
     * 查找用户任务列表
     *
     * @param uid
     * @return
     */
    @Override
    public TaskList findByUid(String uid) {
        // 检查参数
        Preconditions.checkArgument(StringUtils.isNotBlank(uid), ResponseCodeEnum.PARAM_REQUIRE);

        return (TaskList) redisTemplate.opsForValue().get(RedisKeyConstants.TASK_LIST + uid);
    }

    /**
     * 将用户每日任务状态修改为完成
     *
     * @param uid
     * @param task 需要修改的任务
     */
    @Override
    public void taskFinish(String uid, Task task) {
        // 检查参数
        Preconditions.checkArgument(StringUtils.isNotBlank(uid) && Objects.nonNull(task), ResponseCodeEnum.PARAM_REQUIRE);

        // 查询对应用户
        TaskList taskList = (TaskList) redisTemplate.opsForValue().get(RedisKeyConstants.TASK_LIST + uid);

        // 校验数据
        Preconditions.checkArgument(Objects.nonNull(taskList), ResponseCodeEnum.DATA_NOT_EXIST);

        // 更新里面数据
        List<Task> list = taskList.getList();

        // 判断用户任务列表里的任务是否存在与传入的任务同样的名称
        if (!taskListUpdate(task, list)) {
            // 直接抛出异常
            throw new CustomException(ResponseCodeEnum.DATA_NOT_EXIST);
        }

        // 该判断是为了排除用户已经完成了当日任务却自发的追加任务并完成的情况
        // 不再判断是否全部完成任务以及按钮是否可以的情况
        if (!taskList.getIsFinish()) {
            // 判断用户当前的任务是否全部完成
            Boolean isFinish = isFinish(list);
            if (isFinish) {
                // 将打卡按钮设为可用
                redisTemplate.opsForValue().set(RedisKeyConstants.LOG_BUTTON + uid, TaskListConstants.CAN_PRESS_LOG_BUTTON);
            }
            taskList.setIsFinish(isFinish);
        }

        redisTemplate.opsForValue().set(RedisKeyConstants.TASK_LIST + uid, taskList);

    }

    /**
     * 查询用户是否能点击打卡按钮
     *
     * @param uid
     * @return
     */
    @Override
    public Integer check(String uid) {
        // 参数校验
        Preconditions.checkArgument(StringUtils.isNotBlank(uid), ResponseCodeEnum.PARAM_REQUIRE);

        return (Integer) redisTemplate.opsForValue().get(RedisKeyConstants.LOG_BUTTON + uid);
    }

    /**
     * 用户手动打卡
     *
     * @param uid
     */
    @Override
    public void press(String uid) {
        // 参数校验
        Preconditions.checkArgument(StringUtils.isNotBlank(uid), ResponseCodeEnum.PARAM_REQUIRE);
        // 查询用户当前的打卡状态
        Integer check = check(uid);
        Preconditions.checkArgument(TaskListConstants.CAN_PRESS_LOG_BUTTON.equals(check), ResponseCodeEnum.SERVER_ERROR);

        redisTemplate.opsForValue().set(RedisKeyConstants.LOG_BUTTON + uid, TaskListConstants.ALREADY_PRESSED_LOG_BUTTON);

        //获取用户当天做题数
        Integer userTodayQuestions = userAnswerDetailsService.countQuestionsByUid(uid);

        DayLog dayLog = DayLog
                .builder()
                .uid(uid)
                .gotPoints(userTodayQuestions) //TODO 获取了用户当天的做题数，待一起测试
                .date(LocalDateTime.now())
                .build();
        dayLogService.save(dayLog);
    }

    //这是总共连续打卡多少天


    @Override
    public Long getDay(String uid) {
        List<DayLog> list = dayLogService.query().eq("uid", uid).ne("is_finish", 1).list();
        // 从今天开始往前数 直到list的最远的一次没打卡的日期 看连续打卡了多少天
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime start = now.minusDays(1);
        AtomicLong continuousDays = new AtomicLong(0);

        while (start.isAfter(LocalDateTime.of(2024, 1, 1, 0, 0))) {
            LocalDateTime finalStart = start;
            if (list.stream().anyMatch(item -> item.getDate().toLocalDate().isEqual(finalStart.toLocalDate()))) {
                continuousDays.incrementAndGet();
                start = start.minusDays(1);
            } else {
                break;
            }
        }

        return continuousDays.get();
    }



    //这是这个月连续打卡多少天
    @Override
    public Long getMDay(String uid) {
        List<DayLog> list = dayLogService.query().eq("uid", uid).ne("is_finish", 1).list();
        // 从今天开始往前数 直到list的最远的一次没打卡的日期 看连续打卡了多少天
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime start = now.minusDays(1);
        AtomicLong continuousDays = new AtomicLong(0);

        // 获取当前月份的第一天
        LocalDateTime firstDayOfMonth = now.withDayOfMonth(1);
        while (start.isAfter(firstDayOfMonth)) {
            LocalDateTime finalStart = start;
            if (list.stream().anyMatch(item -> item.getDate().toLocalDate().isEqual(finalStart.toLocalDate()))) {
                continuousDays.incrementAndGet();
                start = start.minusDays(1);
            } else {
                break;
            }
        }
        return continuousDays.get();
    }

    /**
     * 判断用户任务列表里的任务是否存在与传入的任务同样的名称
     *
     * @param task
     * @param list
     * @return
     */
    private Boolean taskListUpdate(Task task, List<Task> list) {
        for (int i = 0; i < list.size(); i++) {
            // 找到任务名相同的数据后对里面的内容进行修改
            Task item = list.get(i);
            if (item.getName().equals(task.getName())) {
                list.set(i, task);
                return true;
            }
        }
        return false;
    }

    /**
     * 判断用户的每日任务是否全部完成
     *
     * @param list
     * @return
     */
    public Boolean isFinish(List<Task> list) {
        for (Task task : list) {
            if (task.getState().equals(TaskConstants.CHALLENGED)) {
                return TaskListConstants.TASK_FAIL;
            }
        }
        return TaskListConstants.TASK_ALL_FINISH;
    }

    /**
     * 每日凌晨4点对用户的每日任务进行扫描
     */
    @Scheduled(cron = "0 0 4 * * ?")
    public void taskListClear() {
        transactionTemplate.execute(status -> {
            try {
                log.info("刷新用户任务列表...");

                // 在此处维护循环边界
                // 第一位用户的uid
                long start = 1000001;
                long end = start + SPACER;

                // 通过 redis 管道获取到用户的任务列表
                List<TaskList> taskLists = redisGetPipelining(start, end);

                // 由于使用了 redis 管道技术 尽管在管道里返回了 null
                // 但 taskLists 里无论如何都会存在一个为 null 的对象
                // 也就是说 taskLists 的 size 始终为 1
                // 这也意味着 CollUtil.isEmpty() 无法判断 taskLists
                // 所以通过 taskLists 里第一个元素来进行判断
                while (Objects.nonNull(taskLists.get(0))) {
                    // 过滤不符合需求的数据
                    List<TaskList> newTaskLists = taskListFilter(taskLists);

                    // 使用redis管道技术批量处理数据
                    redisSetPipelining(newTaskLists);

                    // 重新定义边界
                    start = end;
                    end = start + SPACER;

                    // 重新获取到用户的任务列表
                    taskLists = redisGetPipelining(start, end);
                }

                log.info("刷新用户任务列表完成");
                return null;
            } catch (Exception e) {
                // 回归事务
                status.setRollbackOnly();
                log.error("===> 刷新用户任务列表失败", e);
                return null;
            }
        });
    }

    /**
     * redis添加管道
     *
     * @param taskLists 批处理数据
     */
    public void redisSetPipelining(List<TaskList> taskLists) {
        redisTemplate.executePipelined(new SessionCallback<>() {
            @Nullable
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                for (TaskList taskList : taskLists) {
                    // 放入 redis 中
                    // 将打卡按钮设为不可按状态
                    redisOperations.opsForValue().set(RedisKeyConstants.LOG_BUTTON + taskList.getUid(), TaskListConstants.NOT_PRESS_LOG_BUTTON);
                    redisOperations.opsForValue().set(RedisKeyConstants.TASK_LIST + taskList.getUid(), taskList);
                }
                return null;
            }
        });
    }

    /**
     * redis获取管道
     *
     * @param start 最开始用户id
     * @param end   循环结束用户id
     * @return
     */
    public List<TaskList> redisGetPipelining(long start, long end) {
        return redisTemplate.executePipelined(new SessionCallback<>() {
            @Nullable
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                // 构建返回数据
                List<TaskList> taskLists = Lists.newArrayList();
                for (long uid = start; uid < end; uid++) {
                    // 获取用户的任务列表
                    TaskList taskList = (TaskList) redisOperations.opsForValue().get(RedisKeyConstants.TASK_LIST + uid);
                    // 获取到数据
                    if (Objects.isNull(taskList)) {
                        return null;
                    }
                    // 添加到列表中
                    taskLists.add(taskList);
                }
                return taskLists;
            }
        });
    }

    /**
     * 过滤不符合需求的数据 同时 记录打卡
     *
     * @param taskLists
     * @return
     */
    public List<TaskList> taskListFilter(List<TaskList> taskLists) {
        // 创建一个列表过滤不符合需求的数据后赋值
        List<TaskList> newTaskLists = Lists.newArrayList();
        for (TaskList taskList : taskLists) {
            // 取得里面的List<Task>
            List<Task> list = taskList.getList();
            // 用户已经将任务全部完成
            if (taskList.getIsFinish()) {
                // 每日打卡
                dayLogService.log(taskList, DayLogConstants.OVER);

                // 清空list里完成的数据
                List<Task> notFinishTasks = getNotFinishTasks(list);
                taskList.setList(notFinishTasks);
                taskList.setIsFinish(TaskListConstants.TASK_FAIL);

                // 放入newTaskLists中
                newTaskLists.add(taskList);
                continue;
            }

            // 将任务没有全部完成的用户进行打卡
            dayLogService.log(taskList, DayLogConstants.NOT_OVER);

            // 排除掉今日没有接任务的用户
            if (list.isEmpty()) {
                continue;
            }

            List<Task> newList = getNotFinishTasks(list);

            // 对taskList重新赋值
            taskList.setList(newList);

            // 放入newTaskLists中
            newTaskLists.add(taskList);
        }

        return newTaskLists;
    }

    private static List<Task> getNotFinishTasks(List<Task> list) {
        // 创建一个新的List<Task>
        List<Task> newList = Lists.newArrayList();
        for (Task task : list) {
            // 如果任务状态是挑战中则赋值到新数组中
            if (task.getState().equals(TaskConstants.CHALLENGED)) {
                newList.add(task);
            }
        }
        return newList;
    }
}
